{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ac881dce",
   "metadata": {},
   "source": [
    "## Technical Tutor Notebook Overview\n",
    "- Loads `.env` configuration, initializes an OpenAI client (`gpt-4o-mini`), and prepares a reusable `timestamp` helper.\n",
    "- Fetches reference material from `https://realpython.com/async-io-python/` via `fetch_website_contents`, trimming to stay within token limits.\n",
    "- Maintains short-term conversation memory (`ConversationMemory`) to keep context across multiple tutoring questions.\n",
    "- Defines `ask_tutor()` to build prompts from the question, conversation history, and scraped notes, then streams replies with `IPython.display`.\n",
    "- Demonstrates the tutor responding to example questions about Python `asyncio`, ending each answer with “Need another example?”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "1c8357bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import os\n",
    "import sys\n",
    "import textwrap\n",
    "from datetime import datetime\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "from openai import OpenAI\n",
    "from IPython.display import Markdown,display,update_display\n",
    "\n",
    "repo_root = \"/Users/karthikpk/development/llm_engineering\"\n",
    "if repo_root not in sys.path:\n",
    "    sys.path.insert(0,repo_root)\n",
    "from week1.scraper import fetch_website_contents\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "3fff7b35",
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv(override=True)\n",
    "openai_api_key= os.getenv(\"OPENAI_API_KEY\")\n",
    "\n",
    "if not openai_api_key:\n",
    "    raise ValueError(\"OPENAI_API_KEY is not set in the environment variables\")\n",
    "\n",
    "client = OpenAI()\n",
    "MODEL = \"gpt-4o-mini\"\n",
    "\n",
    "timestamp = lambda: datetime.now().strftime(\"%H:%M:%S\")\n",
    "\n",
    "source_url = {\n",
    "    \"async-guide\": \"https://realpython.com/async-io-python/\",\n",
    "}\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "047ad576",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "def get_source_notes(url:str,max_chars:int = 2000) -> str:\n",
    "    print(f\"[{timestamp()}] Fetching {url}...\")\n",
    "    try:\n",
    "        text=fetch_website_contents(url)\n",
    "    except Exception as exec:\n",
    "        text= f\"(Failed to fetch {url}: {exec})\"\n",
    "    return text[:max_chars]\n",
    "\n",
    "\n",
    "def build_context() -> str:\n",
    "    snippets= []\n",
    "    for name,url in  source_url.items():\n",
    "        raw = get_source_notes(url)\n",
    "        snippets.append(f\"### {name}\\nSource: {url}\\n\\n{raw}\")\n",
    "    combined = \"\\n\\n\".join(snippets)\n",
    "    return combined[:4000]\n",
    "\n",
    "session_context = build_context()\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "6783af93",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ConversationMemory:\n",
    "    def __init__(self,max_entries: int = 5, max_chars: int = 2000):\n",
    "        self.entries = []\n",
    "        self.max_entries = max_entries\n",
    "        self.max_chars = max_chars\n",
    "\n",
    "    def add_entry(self,question: str, answer:str)-> None:\n",
    "        self.entries.append({\"question\": question.strip(), \"answer\": answer.strip()})\n",
    "        if len(self.entries)> self.max_entries:\n",
    "            self.entries=self.entries[-self.max_entries:]\n",
    "    \n",
    "    def build_context(self) -> str:\n",
    "        chunks = []\n",
    "        running  = 0\n",
    "        for item in reversed(self.entries):\n",
    "            block = textwrap.dedent(f\"\"\"\n",
    "            Q: {item['question']}\n",
    "            A:{item['answer']}\n",
    "            \"\"\").strip()\n",
    "            if running + len(block) > self.max_chars:\n",
    "                break\n",
    "            chunks.append(block)\n",
    "            running+=len(block)\n",
    "        return \"\\n\\n\".join(reversed(chunks))\n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "6d7be2b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory = ConversationMemory(max_entries=5,max_chars=2000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "3996acd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Tutor answer (with streaming)\n",
    "tutor_system_prompt = textwrap.dedent(\"\"\"\n",
    "    You are a friendly technical tutor.\n",
    "    Use the provided context to explain concepts clearly.\n",
    "    Prefer short paragraphs and include a quick tip or suggestion.\n",
    "    End your reply with: \"Need another example?\"\n",
    "\"\"\").strip()\n",
    "\n",
    "\n",
    "def ask_tutor(question: str) -> None:\n",
    "    past_context=memory.build_context()\n",
    "    user_prompt = textwrap.dedent(f\"\"\"\n",
    "    Question:\n",
    "     {question}\n",
    "    Recent conversation(most recent last):\n",
    "    {past_context or \"No previous conversation yet.\"}\n",
    "    Reference notes:\n",
    "    {session_context}\n",
    "    \"\"\"\n",
    "    ).strip()\n",
    "\n",
    "    stream = client.chat.completions.create(\n",
    "        model=MODEL,\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": tutor_system_prompt},\n",
    "            {\"role\": \"user\", \"content\": user_prompt},\n",
    "        ],\n",
    "        stream=True,\n",
    "    )\n",
    "\n",
    "\n",
    "    reply, handle = \"\" , display(Markdown(\"\"),display_id=True)\n",
    "    for chunk in stream:\n",
    "        delta = chunk.choices[0].delta.content or \"\"\n",
    "        reply+=delta\n",
    "        update_display(Markdown(reply),display_id=handle.display_id)\n",
    "    memory.add_entry(question,reply)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "654dd532",
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"Whats asyncio?\"\n",
    "ask_tutor(question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af9b57c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"Whats my first question?\"\n",
    "ask_tutor(question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "722275aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"Whats my second question?\"\n",
    "ask_tutor(question)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
